package org.ust.extraction;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class Uncompressor
{
	private File	uncompressFile;
	private File	destinationDir;

	public Uncompressor ( File uncompressFile ) throws NotZipFileException
	{
		if ( isSpecifiedFile ( uncompressFile , "jar" )
				|| isSpecifiedFile ( uncompressFile , "zip" ) )
		{
			setUncompressFile ( uncompressFile );
		}
		else
		{
			throw new NotZipFileException (
					"Given file is not in zip/jar format." );
		}
	}

	/**
	 * Begin uncompression.
	 * @return	the uncompressed folder directory.
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public File uncompress ( ) throws FileNotFoundException , IOException
	{
		File file = new File ( uncompress ( getUncompressFile ( ) ) );
		setDestinationDir ( file );
		return getDestinationDir ( );
	}

	private String uncompress ( File uncompressFile )
			throws FileNotFoundException , IOException
	{
		String path = uncompressFile.getAbsolutePath ( );
		String dir = path.substring ( 0 , path.lastIndexOf ( "." ) );
		File dirFile = new File ( dir );
		if ( dirFile.exists ( ) && dirFile.isDirectory ( ) )
		{
			return dir;
		}
		if ( isSpecifiedFile ( uncompressFile , "zip" ) )
		{
			doUnzip ( path , dir );
		}
		else
		{
			doUnjar ( path , dir );
		}
		return dir;
	}

	@ SuppressWarnings ( "unchecked" )
	private void doUnjar ( String inputZip , String destinationDirectory )
			throws FileNotFoundException , IOException
	{
		int BUFFER = 2048;
		List < String > jarFiles = new ArrayList < String > ( );
		File sourceZipFile = new File ( inputZip );
		File unzipDestinationDirectory = new File ( destinationDirectory );
		unzipDestinationDirectory.mkdir ( );

		JarFile jarFile;
		// Open Zip file for reading
		jarFile = new JarFile ( sourceZipFile , false , JarFile.OPEN_READ );

		// Create an enumeration of the entries in the zip file
		Enumeration jarFileEntries = jarFile.entries ( );

		// Process each entry
		while ( jarFileEntries.hasMoreElements ( ) )
		{
			// grab a zip file entry
			JarEntry entry = ( JarEntry ) jarFileEntries.nextElement ( );

			String currentEntry = entry.getName ( );
			System.out.println ( "Extracting " + currentEntry );

			File destFile = new File ( unzipDestinationDirectory , currentEntry );

			// TODO: uncomment this script if flattening the extracted folder.
			// destFile = new File ( unzipDestinationDirectory , destFile
			// .getName ( ) );

			if ( currentEntry.endsWith ( ".jar" ) )
			{
				jarFiles.add ( destFile.getAbsolutePath ( ) );
			}

			// grab file's parent directory structure
			File destinationParent = destFile.getParentFile ( );

			// create the parent directory structure if needed
			destinationParent.mkdirs ( );

			try
			{
				// extract file if not a directory
				if ( ! entry.isDirectory ( ) )
				{
					BufferedInputStream is = new BufferedInputStream ( jarFile
							.getInputStream ( entry ) );
					int currentByte;
					// establish buffer for writing file
					byte data[] = new byte [ BUFFER ];

					// write the current file to disk
					FileOutputStream fos = new FileOutputStream ( destFile );
					BufferedOutputStream dest = new BufferedOutputStream ( fos ,
							BUFFER );

					// read and write until last byte is encountered
					while ( ( currentByte = is.read ( data , 0 , BUFFER ) ) != - 1 )
					{
						dest.write ( data , 0 , currentByte );
					}
					dest.flush ( );
					dest.close ( );
					is.close ( );
				}
			}
			catch ( IOException ioe )
			{
				ioe.printStackTrace ( );
			}
		}
		jarFile.close ( );

		for ( Iterator < String > iter = jarFiles.iterator ( ) ; iter
				.hasNext ( ) ; )
		{
			String zipName = ( String ) iter.next ( );
			doUnjar ( zipName , destinationDirectory + File.separatorChar
					+ zipName.substring ( 0 , zipName.lastIndexOf ( ".jar" ) ) );
		}
	}

	@ SuppressWarnings ( "unchecked" )
	private void doUnzip ( String inputZip , String destinationDirectory )
			throws FileNotFoundException , IOException
	{
		int BUFFER = 2048;
		List < String > zipFiles = new ArrayList < String > ( );
		File sourceZipFile = new File ( inputZip );
		File unzipDestinationDirectory = new File ( destinationDirectory );
		unzipDestinationDirectory.mkdir ( );

		ZipFile zipFile;
		// Open Zip file for reading
		zipFile = new ZipFile ( sourceZipFile , ZipFile.OPEN_READ );

		// Create an enumeration of the entries in the zip file
		Enumeration zipFileEntries = zipFile.entries ( );

		// Process each entry
		while ( zipFileEntries.hasMoreElements ( ) )
		{
			// grab a zip file entry
			ZipEntry entry = ( ZipEntry ) zipFileEntries.nextElement ( );

			String currentEntry = entry.getName ( );

			File destFile = new File ( unzipDestinationDirectory , currentEntry );

			// TODO: uncomment this script if flattening the extracted folder.
			// destFile = new File ( unzipDestinationDirectory , destFile
			// .getName ( ) );

			if ( currentEntry.endsWith ( ".zip" ) )
			{
				zipFiles.add ( destFile.getAbsolutePath ( ) );
			}

			// grab file's parent directory structure
			File destinationParent = destFile.getParentFile ( );

			System.out.println ( "Extracting " + destFile );
			// create the parent directory structure if needed
			destinationParent.mkdirs ( );

			try
			{
				// extract file if not a directory
				if ( ! entry.isDirectory ( ) )
				{
					BufferedInputStream is = new BufferedInputStream ( zipFile
							.getInputStream ( entry ) );
					int currentByte;
					// establish buffer for writing file
					byte data[] = new byte [ BUFFER ];

					// write the current file to disk
					FileOutputStream fos = new FileOutputStream ( destFile );
					BufferedOutputStream dest = new BufferedOutputStream ( fos ,
							BUFFER );

					// read and write until last byte is encountered
					while ( ( currentByte = is.read ( data , 0 , BUFFER ) ) != - 1 )
					{
						dest.write ( data , 0 , currentByte );
					}
					dest.flush ( );
					dest.close ( );
					is.close ( );
				}
			}
			catch ( IOException ioe )
			{
				ioe.printStackTrace ( );
			}
		}
		zipFile.close ( );

		for ( Iterator < String > iter = zipFiles.iterator ( ) ; iter
				.hasNext ( ) ; )
		{
			String zipName = ( String ) iter.next ( );
			doUnzip ( zipName , destinationDirectory + File.separatorChar
					+ zipName.substring ( 0 , zipName.lastIndexOf ( ".zip" ) ) );
		}

	}

	public boolean isSpecifiedFile ( File zipFile , String ext )
	{
		int lastIndexOfDot = zipFile.getName ( ).lastIndexOf ( "." );
		int length = zipFile.getName ( ).length ( );
		String t = zipFile.getName ( ).substring ( lastIndexOfDot + 1 , length );

		return t.equals ( ext );
	}

	public void setUncompressFile ( File uncompressFile )
	{
		this.uncompressFile = uncompressFile;
	}

	public File getUncompressFile ( )
	{
		return uncompressFile;
	}

	public void setDestinationDir ( File destinationDir )
	{
		this.destinationDir = destinationDir;
	}

	public File getDestinationDir ( )
	{
		return destinationDir;
	}

}
